<?php
namespace hongmo;

use think\Exception;
use think\exception\ErrorException;
use think\exception\HttpException;

class Hongmo
{
    private $error = null;  // 错误信息
    private $fd = null;     // 文件描述符
    public $serial = null;  // 端口号，如：com1

    /**
     * Hongmo constructor.
     * @param string $com 端口号如：com1，默认在 hongmo.default_port中配置
     */
    public function __construct($com = null)
    {
        // 配置虹膜设备端口号
        if (!empty($com)) {
            $com_value = '';
            if (!preg_match("/com[\d]+/",$com,$com_value)) {
                $this->error = '端口号有误';
                return false;
            }
            $this->serial = $com;
        } else {
            $this->serial = config('hongmo.default_port');
        }
        if (empty($this->serial)) {
            exit('串口号不能为空');
        }
    }

    public function getError()
    {
        return $this->error;
    }

    /**
     * 创建Dio连接
     * @return bool
     */
    private function dioOpen()
    {
        exec("mode $this->serial: baud=115200 data=8 stop=1 parity=n xon=off");

        try {
            $this->fd = dio_open("$this->serial:", O_RDWR);
        }catch(ErrorException $e) {
            $this->error = '虹膜设备连接失败，请稍等5秒后重试';
            return false;
        }

        $dio_stat = dio_stat($this->fd);

        if (!$this->fd || !$dio_stat) {
            $this->error = "端口打开失败,请检查 " . $this->serial . " 端口是否被占用";
            return false;
        }

        return true;
    }

    /**
     * 字符转十六进制
     * @param string $string
     * @return string
     */
    private function _str2hex($string)
    {
        $str = '';
        for ($i = 0; $i < strlen($string); $i += 2) {
            $str .= chr(hexdec(substr($string, $i, 2)));
        }
        return $str;
    }

    /**
     * 字符转十进制
     * @param $str
     * @return string
     */
    private function strToHex($str)
    {
        $hex = "";
        for ($i = 0; $i < strlen($str); $i++)
            $hex .= empty(dechex(ord($str[$i]))) || dechex(ord($str[$i])) == '' ? '00' : str_pad(dechex(ord($str[$i])), 2, 0, STR_PAD_LEFT);
        $hex = strtoupper($hex);
        return $hex;
    }

    /**
     * 写入数据
     * @param string $str
     * @return mixed
     */
    private function dioWrite($str)
    {
        if (!$this->device_start()) return false;
        $str = $this->_str2hex($str);
        $wLen = dio_write($this->fd, $str);
        return $wLen;
    }

    /**
     * 关闭Dio连接
     * @param $serial
     */
    private function dioClose()
    {
        dio_close($this->fd);
    }

    /**
     * 读取Dio数据
     * @return mixed
     */
    private function dioRead()
    {
        return dio_read($this->fd);
    }

    /**
     * 解析
     * @param $ccc33
     * @param string $pa
     * @return string|string[]
     */
    private function jiexi($ccc33, $pa = '')
    {
        $valus = bin2hex($ccc33);
        return str_replace($pa, '', $valus);
    }

    /**
     * 用'3'拼接输入的数组每一项,返回48位长度字符,不足48位在前面用'0'补充
     * @param array $input
     * @return string
     */
    private function padstr3($input = [])
    {
        $str = 0;
        if ($input !== 0) {
            $str = '';
            foreach ($input as $k => $v) {
                $str .= '3' . $v;
            }
        }
        return str_pad($str, 48, "0", STR_PAD_LEFT);
    }

    /**
     * 校验数据并拼接帧结束符
     * @param $str
     * @return string
     */
    private function xorphp($str)
    {
        $xor = pack('H*', substr($str, 0, 2));
        for ($i = 2; $i < strlen($str); $i += 2) {//dechex
            $ccc = substr($str, $i, 2);
            $xor = $xor ^ pack('H*', $ccc);
        }
        $xorstr = $this->strToHex(~$xor);
        return $xorstr . '03';
    }

    /**
     * 注册用户
     * @param string $userid 用户ID
     * @return bool
     */
    public function zhuceyongh($userid)
    {
        $str = '5500220000000001';
        $minhglingbianma = '43';
        $length = '0018';
        $userid = $this->padstr3(str_split($userid));
        $constr = $str . $minhglingbianma . $length . $userid;
        $xor = $this->xorphp($constr);
        if (!$this->dioOpen()) return false;
        if (!$this->dioWrite($constr . $xor)) return false;
        $ccc33 = $this->dioRead();
        $this->dioClose();
        $jiexi = $this->jiexi($ccc33, '55000b00000000014300');
        $jieguo = substr($jiexi, 2, 2);

        if ($jieguo == '00') {
            return true;
        } else {
            //原因有很多
            $yuanyinarr = [
                '01' => '注册失败', '02' => '为不同ID重复注册',
                '03' => '为相同ID注册', '04' => '为无可用空间存储模板',
                '05' => 'ID为空', '06' => '无效的ID',
                '07' => '模板数据为空', '08' => '注册流程忙',
                '09' => '虹膜硬件不可用', '0a' => '注册超时',
                '0b' => '取消注册', '0c' => '加载虹膜库失败',
                '0e' => '连接相机失败', '0f' => '检测到假体注册',
                '10' => '虹膜库授权过期'
            ];
            $msg = empty($yuanyinarr[$jieguo]) ? '非法操作' : $yuanyinarr[$jieguo];
            $this->error = $msg;
            return false;
        }
    }

    /**
     * 发送启动命令，设备每次开机在发送启动命令后才能进行其他操作
     * @return bool
     */
    private function device_start()
    {
        // 启动过就不再发送启动命令
        if (session('hongmo_device_started')) return true;
        session('hongmo_device_started',true);
        // 通信已打开则关闭
        if ($this->fd) {
            $this->dioClose();
        }

        // 调用通信检查客户端
        $serial_path = str_replace('\\', '/', __DIR__);

        if (!is_file($serial_path . '/start.vbs')) {
            $this->error = '虹膜通讯程序丢失';
            return false;
        }
        $cmd = "cd $serial_path && start start.vbs && exit 2>&1";
        exec($cmd);
        sleep(2);
        if (is_dir($serial_path)) deleteDir("$serial_path/%SystemDrive%");

        // 重新建立通信
        if (!$this->dioOpen()) return false;
        return true;
    }

    //识别的 返回id
    public function shibiede()
    {
        // 55 00 22 00 00 00 00 01 42 00 18 000000000000000000000000000000000000000000000000 d3 03
        $str = '5500220000000001';
        $minhglingbianma = '42';
        $length = '0018';
        $userid = $this->padstr3(0);
        $constr = $str . $minhglingbianma . $length . $userid;
        $xor = $this->xorphp($constr);
        if (!$this->dioOpen()) return false;
        if (!$this->dioWrite($constr . $xor)) return false;
        $ccc33 = $this->dioRead();
        $this->dioClose();
        $jiexi = $this->jiexi($ccc33, '5500220000000001420018');
        $jiexi = ltrim($jiexi, '0');
        //除了最后四个 剩下的就是id
        $jiexi = substr($jiexi, 0, -4);
        $mtas = $this->ersanguo($jiexi);
        //$jiexi
        if ($mtas == false || $mtas == '0' || $mtas == '') {
            $this->error = "获取失败！请重新尝试";
            return false;
        } else {
            return $mtas;
        }
    }

    //需要自己写一个 2三过的方法
    private function ersanguo($str)
    {
        $sss = '';
        for ($i = 0; $i < strlen($str); $i += 2) {//dechex
            $sss .= strval(substr($str, $i + 1, 1));
        }
        return $sss;
    }

    //删除模版
    public function delmoabn($userid)
    {
        // 55 00 22 00 00 00 00 01 44 00 18 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 D5 03
        $str = '5500220000000001';
        $minhglingbianma = '44';
        $length = '0018';
        $userid = $this->padstr3(str_split($userid, 1));
        $constr = $str . $minhglingbianma . $length . $userid;
        $xor = $this->xorphp($constr);
        if (!$this->dioOpen()) return false;
        if (!$this->dioWrite($constr . $xor)) return false;
        $ccc33 = $this->dioRead();
        $this->dioClose();
        $jiexi = $this->jiexi($ccc33, '55000b00000000014400');
        $jieguo = substr($jiexi, 2, 2);
//        if ($jieguo == '00') {
//            return true;
//        } else {
//            $this->error = "删除失败，未找到用户";
//            return false;
//        }
        return true;
    }
}


// 使用说明

// 注册虹膜
// use hongmo\Hongmo;
// $hongmo = new Hongmo();
// $result = $hongmo->zhuceyongh($id);
// if (!$result) $this->error_json($hongmo->getError());

// 虹膜扫描识别用户ID
// use hongmo\Hongmo;
// $hongmo = new Hongmo();
// $user_id = $hongmo->shibiede();
// if (!$user_id) $this->error_json($hongmo->getError());


// 删除用户
// use hongmo\Hongmo;
// $hongmo = new Hongmo();
// $res = $hongmo->delmoabn($value);
// if (!$res) $this->error_json($hongmo->getError());


